/**
 * Copyright (C) 2007 The AsyncMail Group <asyncmail@googlegroups.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.asyncmail.smtpserver.handler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.springframework.beans.factory.annotation.Required;

import com.googlecode.asyncmail.common.ExtensibleHandler;
import com.googlecode.asyncmail.smtp.SMTPRequest;
import com.googlecode.asyncmail.smtp.SMTPRequestImpl;
import com.googlecode.asyncmail.smtp.SMTPResponse;
import com.googlecode.asyncmail.smtp.SMTPResponseImpl;
import com.googlecode.asyncmail.smtp.SMTPRetCode;
import com.googlecode.asyncmail.smtpserver.SMTPSession;
import com.googlecode.asyncmail.smtpserver.core.CommandHandler;
import com.googlecode.asyncmail.smtpserver.hook.HookResult;
import com.googlecode.asyncmail.smtpserver.hook.HookResultHook;
import com.googlecode.asyncmail.smtpserver.hook.HookReturnCode;

/**
 * Abstract class which Handle hooks.
 * 
 */
public abstract class AbstractHookableCmdHandler implements CommandHandler,
        ExtensibleHandler {

    private List<?> hooks;

    private List<? super HookResultHook> rHooks;

    private Log logger;

    @Required
    public void setLogger(Log logger) {
        this.logger = logger;
    }

    protected Log getLogger() {
        return logger;
    }

    /**
     * Handle command processing
     * 
     * @see com.googlecode.asyncmail.smtpserver.core.CommandHandler#onCommand(com.googlecode.asyncmail.smtpserver.SMTPSession,
     *      SMTPRequestImpl)
     */
    public SMTPResponse onCommand(SMTPSession session, SMTPRequest command) {
        SMTPResponse response = doFilterChecks(session, command.getCommand(),
                command.getArgument());

        if (response == null) {
            return processHooks(session, command.getCommand(), command
                    .getArgument());
        } else {
            return response;
        }

    }

    /**
     * Process all hooks for the given command
     * 
     * @param session
     *                the SMTPSession object
     * @param command
     *                the command
     * @param parameters
     *                the paramaters
     * @return SMTPResponseImpl
     */
    private SMTPResponse processHooks(SMTPSession session, String command,
            String parameters) {
        List<?> hooks = getHooks();
        if (hooks != null) {
            int count = hooks.size();
            for (int i = 0; i < count; i++) {
                Object rawHook = hooks.get(i);
                getLogger().debug(
                        "executing hook " + rawHook.getClass().getName());
                HookResult hRes = callHook(rawHook, session, parameters);
                if (rHooks != null) {
                    for (int k = 0; k < rHooks.size(); k++) {
                        Object rHook = rHooks.get(k);
                        getLogger().debug("executing hook " + rHook);
                        hRes = ((HookResultHook) rHook).onHookResult(session,
                                hRes, rawHook);
                    }
                }
                SMTPResponse res = HookResultToSMTPResponse(hRes);
                if (res != null) {
                    return res;
                }
            }
        }
        return doCoreCmd(session, command, parameters);
    }

    /**
     * Must be implemented by hookable cmd handlers to make the effective call
     * to an hook.
     * 
     * @param rawHook
     *                the hook (to be casted)
     * @param session
     *                the session
     * @param parameters
     *                the parameters
     * @return the HookResult, will be calculated using
     *         HookResultToSMTPResponse.
     */
    protected abstract HookResult callHook(Object rawHook, SMTPSession session,
            String parameters);

    /**
     * Convert the HookResult to SMTPResponseImpl using default values. Should be
     * override for using own values
     * 
     * @param result
     * @return
     */
    protected SMTPResponse HookResultToSMTPResponse(HookResult result) {
        return calcDefaultSMTPResponse(result);
    }

    /**
     * @param result
     */
    public static SMTPResponseImpl calcDefaultSMTPResponse(HookResult result) {
        if (result != null) {
            int rCode = result.getResult();
            String smtpRetCode = result.getSmtpRetCode();
            String smtpDesc = result.getSmtpDescription();

            if (rCode == HookReturnCode.DENY) {
                if (smtpRetCode == null)
                    smtpRetCode = SMTPRetCode.TRANSACTION_FAILED;
                if (smtpDesc == null)
                    smtpDesc = "Email rejected";

                return new SMTPResponseImpl(smtpRetCode, smtpDesc);
            } else if (rCode == HookReturnCode.DENYSOFT) {
                if (smtpRetCode == null)
                    smtpRetCode = SMTPRetCode.LOCAL_ERROR;
                if (smtpDesc == null)
                    smtpDesc = "Temporary problem. Please try again later";

                return new SMTPResponseImpl(smtpRetCode, smtpDesc);
            } else if (rCode == HookReturnCode.OK) {
                if (smtpRetCode == null)
                    smtpRetCode = SMTPRetCode.MAIL_OK;
                if (smtpDesc == null)
                    smtpDesc = "Command accepted";

                return new SMTPResponseImpl(smtpRetCode, smtpDesc);
            } else {
                // TODO !? What do we have to do? Is there a default?
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Execute Syntax checks and return a SMTPResponseImpl if a syntax error was
     * detected, otherwise null.
     * 
     * @param session
     * @param command
     * @param parameters
     * @return
     */
    protected abstract SMTPResponse doFilterChecks(SMTPSession session,
            String command, String parameters);

    /**
     * Execute the core commandHandling.
     * 
     * @param session
     * @param command
     * @param parameters
     * @return
     */
    protected abstract SMTPResponse doCoreCmd(SMTPSession session,
            String command, String parameters);

    /**
     * @see com.googlecode.asyncmail.common.ExtensibleHandler#getMarkerInterfaces()
     */
    public List<Class<?>> getMarkerInterfaces() {
        List<Class<?>> classes = new ArrayList<Class<?>>(2);
        classes.add(getHookInterface());
        classes.add(HookResultHook.class);
        return classes;
    }

    /**
     * Return the interface which hooks need to implement to hook in
     * 
     * @return interface
     */
    protected abstract Class<?> getHookInterface();

    /**
     * @see com.googlecode.asyncmail.common.ExtensibleHandler#wireExtensions(java.lang.Class,
     *      java.util.List)
     */
    @SuppressWarnings("unchecked")
    public void wireExtensions(Class<?> interfaceName, List<?> extension) {
        if (getHookInterface().equals(interfaceName)) {
            this.hooks = extension;
        } else if (HookResultHook.class.equals(interfaceName)) {
            // If a clean up hook is defined let's add it to the extensions.
            if (getCleanupHook() != null) {
                this.rHooks = new LinkedList<HookResultHook>(
                        (Collection<? extends HookResultHook>) extension);
                this.rHooks.add(0, getCleanupHook());
            } else {
                this.rHooks = (List<? super HookResultHook>) extension;
            }
        }

    }

    /**
     * Return a list which holds all hooks for the cmdHandler
     * 
     * @return
     */
    protected List<?> getHooks() {
        return hooks;
    }

    /**
     * TODO Define wether the cleanup hook have to be called before or after the
     * others hookresulthooks. Currently we add it as the first filter to be
     * sure it will be called always.
     * 
     * @return the cleanup hook.
     */
    protected HookResultHook getCleanupHook() {
        return null;
    }

}
